-- 这个文件是全局可以调用的函数和私货
local function RGB(r, g, b)
    return {r / 255, g / 255, b / 255, 1}
end

COLORS = {
    WHITE = RGB(255, 255, 255),
    BLACK = RGB(0, 0, 0),
    RED = RGB(207, 61, 61),
    PINK = RGB(255, 192, 203),
    YELLOW = RGB(255, 255, 0),
    BLUE = RGB(0, 0, 255),
    GREEN = RGB(59, 222, 99),
    PURPLE = RGB(184, 87, 198),
    BROWN = RGB(127, 76, 51)
}
function InGame()
    return GLOBAL.ThePlayer and GLOBAL.ThePlayer.HUD and not GLOBAL.ThePlayer.HUD:HasInputFocus()
    -- 允许打开制作栏时使用功能，输入的时候不让使用就行了
    --  and not GLOBAL.ThePlayer.HUD:IsCraftingOpen()
end

function Say(str)
    local talker = GLOBAL.ThePlayer.components.talker
    if talker then
        talker:Say(str)
    end
end

function TIP(stat, color, content, way)
    if GLOBAL.GetTimePlaying() < 5 then return end
    if type(stat) == "string" and type(color) == "nil" and type(content) == "nil" and type(way) == "nil" then
        return GLOBAL.ChatHistory:AddToHistory(GLOBAL.ChatTypes.Message, nil, nil, "Message", stat, COLORS.GREEN)
    end
    local loca = way or GetModConfigData("sw_tip")
    local tcolor = color and COLORS[string.upper(color)] or COLORS.WHITE
    if type(content) == "nil" then
        return GLOBAL.ThePlayer.components.talker:Say(stat, nil, nil, nil, nil, tcolor)
    end

    if type(content) == "boolean" and content then
        tcolor = COLORS.GREEN
        content = "开启"
    end
    if type(content) == "boolean" and not content then
        tcolor = COLORS.RED
        content = "关闭"
    end

    if InGame() and GLOBAL.ThePlayer.components.talker then
        if loca == "announce" then
            GLOBAL.TheNet:Say(stat .. "：" .. content)
        elseif loca == "head" then
            GLOBAL.ThePlayer.components.talker:Say(stat .. "：" .. content, nil, nil, nil, nil, tcolor)
        elseif loca == "chat" then
            GLOBAL.ChatHistory:AddToHistory(GLOBAL.ChatTypes.Message, nil, nil, stat, content, tcolor)
        else
            GLOBAL.ThePlayer.components.talker:Say(stat .. "：" .. content, nil, nil, nil, nil, tcolor)
        end
    end
end

-- 这两表存数据呢
MUSHROOM_ban_list = {}
MUSHROOM_con_mod_list = {}

-- 返回value集合但什么都不查
function mergeTable(...)
    local mTable = {}
    for _, v in pairs({...}) do
        if type(v) == "table" then
            for _, k in pairs(v) do
                table.insert(mTable, k)
            end
        end
    end
    return mTable
end

-- 获取装备物品
function GetEquippedItemFrom(slot)
    return GLOBAL.ThePlayer and GLOBAL.ThePlayer.replica.inventory and GLOBAL.ThePlayer.replica.inventory:GetEquippedItem(slot)
end
-- 获取容器内的物品
function GetItemsFromOpenContainer()
    local items = {}
    if not GLOBAL.ThePlayer or not GLOBAL.ThePlayer.replica.inventory then
        return items
    end

    local lastitems = {}
    for container, v in pairs(GLOBAL.ThePlayer.replica.inventory:GetOpenContainers()) do
        if container and container.replica and container.replica.container then
            local items_container = container.replica.container:GetItems()
            if container:HasTag("INLIMBO") then
                lastitems = mergeTable(lastitems, items_container)
            else
                items = mergeTable(items, items_container)
            end
        end
    end
    return mergeTable(items, lastitems)
end

-- 获取所有物品（未指定prefab则获取全部）
function GetItemsFromAll(prefab, alltags)
    local items = {}
    local initems = GLOBAL.ThePlayer and GLOBAL.ThePlayer.replica.inventory:GetItems() or {}
    local equipitems = GLOBAL.ThePlayer and GLOBAL.ThePlayer.replica.inventory:GetEquips() or {}
    local containeritems = GetItemsFromOpenContainer() or {}
    for _, ent in pairs(mergeTable(containeritems, equipitems, initems)) do
        if ((prefab == ent.prefab)
            or ((type(prefab) == "table" and table.contains(prefab, ent.prefab)) 
            or (type(prefab) == "nil")))
        then
            local allflag = true
            if type(alltags) == "table" then
                allflag = ent:HasTags(alltags)
            elseif type(alltags) == "string"then
                allflag = ent:HasTag(alltags)
            end
            if allflag then
                table.insert(items, ent)
            end
        end
    end
    return items
end
-- 获取所有容器的单个物品
function GetItemFromAll(prefab, alltags, func)
    local prefabs = GetItemsFromAll(prefab, alltags)
    for _, v in pairs(prefabs) do
        if not func or func(v) then
            return v
        end
    end
end

-- 发送RPC
-- 燃料A加到B
function SendRPCAtoB(rpc, action, A, B)
    local buffact = GLOBAL.BufferedAction(GLOBAL.ThePlayer, B, action, A)
    if not GLOBAL.TheWorld.ismastersim then
        local function cb()
            GLOBAL.SendRPCToServer(rpc, action.code, B, A, action.mod_name)
        end
        if GLOBAL.ThePlayer.components.locomotor then
            buffact.preview_cb = cb
        else
            cb()
        end
    end
    GLOBAL.ThePlayer.components.playercontroller:DoAction(buffact)
end

-- 用A召唤收回B
-- 右键单击T
function SendRPCAwithB(rpc, action, A, B)
    local buffact = GLOBAL.BufferedAction(GLOBAL.ThePlayer, B, action, A)
    if not GLOBAL.TheWorld.ismastersim then
        local function cb()
            GLOBAL.SendRPCToServer(rpc, action.code, A, B, action.mod_name)
        end
        if GLOBAL.ThePlayer.components.locomotor then
            buffact.preview_cb = cb
        else
            cb()
        end
    end
    GLOBAL.ThePlayer.components.playercontroller:DoAction(buffact)
end

-- 左键点击某个位置
function SendRPCclk(rpc, action, position)
    local buffact = GLOBAL.BufferedAction(GLOBAL.ThePlayer, nil, action, nil, position)
    if not GLOBAL.TheWorld.ismastersim then
        local function cb()
            GLOBAL.SendRPCToServer(rpc, action.code, position.x, position.z, nil, false, 0, true)
        end
        if GLOBAL.ThePlayer.components.locomotor then
            buffact.preview_cb = cb
        else
            cb()
        end
    end
    GLOBAL.ThePlayer.components.playercontroller:DoAction(buffact)
end

-- 模组检测器 item是否在str里
function str_contains(str, item)
    -- 将两个字符串都转换为小写，并去除空格
    if type(str) ~= "string" or type(item) ~= "string" then
        return
    end
    str = string.lower(str):gsub("%s+", "")
    item = string.lower(item):gsub("%s+", "")
    -- 判断str是否包含item，返回布尔值
    return string.find(str, item) ~= nil
end

local modsnametable = {}
local allMods = GLOBAL.KnownModIndex:GetModsToLoad()
for k, v in pairs(allMods) do
    local themod = GLOBAL.KnownModIndex:GetModInfo(v)
    if themod and themod.name and themod.version then
        table.insert(modsnametable, themod.name..(themod.version))
    end
end

function HasModName(modname)
    -- 服务器包含此模组时返回 服务器重复的模组名
    -- 不包含时返回false
    if not GetModConfigData("sw_debugger") then
        return false
    end
    local mods = allMods
    for key, name in pairs(modsnametable) do
        if name and str_contains(name, modname) then                
            return name
        end
    end
    return false
end

function IsInMODlist(modlist)
    if type(modlist) ~= "table" then
        return HasModName(modlist)
    end
    for _, modname in pairs(modlist) do
        if HasModName(modname) then
            return true
        end
    end
    return false
end

function c_anim(anims, ent)
    if not ent then
        return
    end
    if type(anims) == "table" then
        for _,anim in pairs(anims)do
            if ent.AnimState and ent.AnimState:IsCurrentAnimation(anim) then
                return true
            end
        end
        return false
    else
        return ent.AnimState and ent.AnimState:IsCurrentAnimation(anims)
    end
end


function PlayerFindEnts(range, allowTags, banTags)
    local pos = GLOBAL.ThePlayer and GLOBAL.ThePlayer:GetPosition()
    -- 事实上默认获取到的已经排序过了
    if type(banTags) == "nil" then
        banTags = {'FX','DECOR','INLIMBO','NOCLICK', 'player'}
    end

    return pos and GLOBAL.TheSim:FindEntities(pos.x, 0, pos.z, range or 80, allowTags, banTags) or {}
end

-- 有效位置
function IsInValidPos(ent)
    return (not GLOBAL.TheWorld:HasTag("cave") and (ent:IsOnValidGround() or not ent:IsOnOcean(false)))
            or (GLOBAL.TheWorld:HasTag("cave") and ent:IsOnValidGround())
end


function PlayerFindEnt(name, range, allowTags, banTags, allowAnims, banAnims, isInValidPos, func)
    if not range then
        range = 80
    end
    if type(allowTags) ~= "table" then
        allowTags = nil
    end
    if type(banTags) ~= "table" then
        banTags = {'FX','DECOR','INLIMBO','NOCLICK', 'player'}
    end
    if type(banAnims) == "nil" then
        banAnims = {"death"}
    end
    local neardist = 6400
    local player_pos = GLOBAL.ThePlayer:GetPosition()
    local all_ents = GLOBAL.TheSim:FindEntities(player_pos.x, 0, player_pos.z, range, allowTags, banTags)
    local nearent
    for _,ent in pairs(all_ents)do
        if ((type(name) == "table" and table.contains(name, ent.prefab)) or (name == ent.prefab) or (type(name) == "nil"))
        and ((allowAnims and c_anim(allowAnims, ent)) or (not allowAnims))
        and (banAnims and not c_anim(banAnims, ent))
        and (isInValidPos and IsInValidPos(ent) or (not isInValidPos))
        and ((not func) or func(ent))
        then
            local dist = ent:GetPosition():DistSq(player_pos)
            if dist and dist < neardist then
                neardist = dist
                nearent = ent
            end
        end
    end
    return nearent
end

function getStacksize(item)
	if item.replica.stackable then
		return item.replica.stackable:StackSize()
	else
		return 1
	end
end

function getPrefabCount(name, range, allowTags, banTags, allowAnims, banAnims, pos, func)
    if not range then
        range = 80
    end
    if type(allowTags) ~= "table" then
        allowTags = nil
    end
    if type(banTags) ~= "table" then
        banTags = {'FX','DECOR','NOCLICK', 'player', 'INLIMBO'}
    end
    local neardist = 6400
    local pos = pos or GLOBAL.ThePlayer:GetPosition()
    local all_ents = GLOBAL.TheSim:FindEntities(pos.x, 0, pos.z, range, allowTags, banTags)
    local count = 0
    for _,ent in pairs(all_ents)do
        if ((type(name) == "table" and table.contains(name, ent.prefab)) or (name == ent.prefab) or (type(name) == "nil"))
        and ((allowAnims and c_anim(allowAnims, ent)) or (not allowAnims))
        and ((banAnims and not c_anim(banAnims, ent)) or (not banAnims))
        and (not func or func(ent))
        then
            count = count + getStacksize(ent)
        end
    end
    return count
end



-- 面板功能绑定
function AddBindBtn(bnd, fn)
    local bind = GetModConfigData(bnd)
    if type(bind) == "number" then
        GLOBAL.TheInput:AddKeyUpHandler(bind, function ()
            if InGame() then fn() end
        end)
    end
end



--- 响应左键右键，提取自管理员面板
-- custom left n right click button handler, based/copied from imagebutton oncontrol
function LROnControl(button, control, down)
	if not button:IsEnabled() or not button.focus or GLOBAL.TheInput:IsControlPressed(GLOBAL.CONTROL_FORCE_INSPECT) then return end
	if button:IsSelected() and not button.AllowOnControlWhenSelected then return false end
	local click = control == button.control and 1 or (control == GLOBAL.CONTROL_SECONDARY or control == GLOBAL.CONTROL_CONTROLLER_ALTACTION) and 2 or 0
	if click == 0 then return end
	if down and not button.down then
		if button.has_image_down then
			button.image:SetTexture(button.atlas, button.image_down)
			if button.size_x and button.size_y then 
				button.image:ScaleToSize(button.size_x, button.size_y)
			end
		end
		GLOBAL.TheFrontEnd:GetSound():PlaySound("dontstarve/HUD/click_move")
		button.o_pos = button:GetLocalPosition()
		if button.move_on_click then
			button:SetPosition(button.o_pos + button.clickoffset)
		end
		button.down = true
		-- to store which button made it down
		button.left = click == 1
		if button.whiledown then
			button:StartUpdating()
		end
		if button.ondown then
			button.ondown()
		end
	-- make sure button released is one that made it down
	elseif not down and button.down and button.left == (click == 1) then
		if button.has_image_down then
			button.image:SetTexture(button.atlas, button.image_focus)
			if button.size_x and button.size_y then 
				button.image:ScaleToSize(button.size_x, button.size_y)
			end
		end
		button.down = false
		button.left = nil
		button:ResetPreClickPosition()
		if button.onclick then
			button.onclick(click == 1)
		end
		button:StopUpdating()
	end
	return true
end

--- copy Tony --
function is_entity(t)
    return t and t.is_a and t:is_a(GLOBAL.EntityScript)
end
-- 获取物品
function GetItemSlot(item)
    if not GLOBAL.ThePlayer and GLOBAL.ThePlayer.replica.inventory then
        return
    end
    for container, v in pairs(GLOBAL.ThePlayer.replica.inventory:GetOpenContainers()) do
        if container and container.replica and container.replica.container and container:HasTag("backpack") then
            local items_container = container.replica.container:GetItems()
            for k, v in pairs(items_container) do
                if v.prefab == item then
                    return container.replica.container, k
                end
            end
        end
    end
    for k, v in pairs(GLOBAL.ThePlayer.replica.inventory:GetItems()) do
        if v.prefab == item then
            return GLOBAL.ThePlayer.replica.inventory, k
        end
    end
end

-- 移动打断
function InterruptedByMobile(get_thread, stop_thread)
    local interrupt_controls = {}
    for control = GLOBAL.CONTROL_ATTACK, GLOBAL.CONTROL_MOVE_RIGHT do
        interrupt_controls[control] = true
    end
    
    AddComponentPostInit("playercontroller", function(self, inst)
        if inst ~= GLOBAL.ThePlayer then return end
        
        inst:ListenForEvent("aqp_threadstart", function(inst) stop_thread() end)
    
        local mouse_controls = {[GLOBAL.CONTROL_PRIMARY] = true, [GLOBAL.CONTROL_SECONDARY] = true}
        
        local PlayerControllerOnControl = self.OnControl
        self.OnControl = function(self, control, down)
            local mouse_control = mouse_controls[control]
            local interrupt_control = interrupt_controls[control]
            if InGame() and (interrupt_control or mouse_control and not GLOBAL.TheInput:IsControlPressed(GLOBAL.CONTROL_FORCE_INSPECT)) then
                if down and get_thread() then
                    stop_thread()
                end
            end
            PlayerControllerOnControl(self, control, down)
        end
    end)
end
-- 右键操作身上的（过时, 推荐用下面的UseItemOnSelf）
function UseItemSelf(item, action_code)
    -- action_code : like ACTIONS.MURDER.code
    if not item then return end
    local inventory = GLOBAL.ThePlayer.components.inventory
    local playercontroller = GLOBAL.ThePlayer.components.playercontroller
    if inventory then
        local playercontroller_deploy_mode = playercontroller.deploy_mode
        playercontroller:ClearControlMods()
        playercontroller.deploy_mode = false
        inventory:ControllerUseItemOnSelfFromInvTile(item, action_code)
        playercontroller.deploy_mode = playercontroller_deploy_mode
    else
        GLOBAL.SendRPCToServer(GLOBAL.RPC.ControllerUseItemOnSelfFromInvTile, action_code, item)
    end
end
-- 按下Shift
function IsScrollModifierDown()
    return GLOBAL.TheInput:IsKeyDown(GLOBAL.rawget(GLOBAL, "KEY_SHIFT"))
end
-- 判断双击间隔
function InDoubleClickTime(cur, last)
    return cur - last < 0.3
end
-- 右键操作视野中的
function UseItemOnScene(item, act)
    local inventory = GLOBAL.ThePlayer.components.inventory
    if inventory then
        inventory:ControllerUseItemOnSceneFromInvTile(item, act.target, act.action.code, act.action.mod_name)
    else
        GLOBAL.ThePlayer.components.playercontroller:RemoteControllerUseItemOnSceneFromInvTile(act, item)
    end
end
-- 右键操作身上的
function UseItemOnSelf(item)
    local playercontroller = GLOBAL.ThePlayer.components.playercontroller
    local playercontroller_deploy_mode = playercontroller.deploy_mode
    playercontroller.deploy_mode = false
    GLOBAL.ThePlayer.replica.inventory:ControllerUseItemOnSelfFromInvTile(item)
    playercontroller.deploy_mode = playercontroller_deploy_mode
end
-- 是否是有效容器
function IsValidContainer(container, cur_items)
    if not (container and container:IsOpenedBy(GLOBAL.ThePlayer)) then return end
    if not container:IsFull() then return true end
    if container:AcceptsStacks() then
        if is_entity(cur_items) then
            cur_items = { cur_items }
        end
        for _, v in pairs(container:GetItems()) do
            for _, cur_item in ipairs(cur_items) do
                if not is_entity(cur_item) then
                    cur_item = cur_item.item
                end
                if v.prefab == cur_item.prefab and v.AnimState:GetSkinBuild() == cur_item.AnimState:GetSkinBuild() then
                    if v.replica.stackable ~= nil and not v.replica.stackable:IsFull() then
                        return true
                    end
                end
            end
        end
    end
end
-- 执行act若关延迟执行fn
function SendActionAndFn(act, fn)
    local playercontroller = GLOBAL.ThePlayer.components.playercontroller
    if playercontroller.ismastersim then
        GLOBAL.ThePlayer.components.combat:SetTarget(nil)
        playercontroller:DoAction(act)
        return
    end

    if playercontroller.locomotor then
        act.preview_cb = fn
        playercontroller:DoAction(act)
    else
        fn()
    end
end

-- 尝试制作某个物品
function TryCraft(inst)
    for recname, rec in pairs(GLOBAL.AllRecipes) do
        if GLOBAL.IsRecipeValid(recname)
            and rec.placer == nil
            and rec.sg_state == nil
            and inst.replica.builder:KnowsRecipe(recname)
            and inst.replica.builder:CanBuild(recname) then

            if inst.components.builder then
                inst.components.builder:MakeRecipeFromMenu(rec)
            else
                GLOBAL.SendRPCToServer(GLOBAL.RPC.MakeRecipeFromMenu, rec.rpc_id)
            end
            return rec
        end
    end
end



-- 文件存储 -- 
-- 文件存储 id的值应该是存储数据的ID, 不同功能绝不应该设置相同的ID
local SavePSData = require("persistentdata")
local DataContainerID = "ModSRC"
local ModDataContainer = SavePSData(DataContainerID)
ModDataContainer:Load()

function SaveModData(id, value)
    if not id then return end
    ModDataContainer:SetValue(id, value)
    ModDataContainer:Save()
end

function LoadModData(id)
    if not id then return end
    return ModDataContainer:GetValue(id)
end


-- 检索动画 --
-- copy Timer --
local function isanim(anim,entity)
    return entity and entity.AnimState and entity.AnimState:IsCurrentAnimation(anim)
end

function GetAnim(ent)
    local tags_string = ent:GetDebugString()
    return tags_string and tags_string:match(" anim: (.-) anim/")
end
function SearchRegularAnim(regular, ent)
    local anim = GetAnim(ent)
    return anim and string.find(anim, regular)
end
function SearchForAnim(anims,entity)
	if type(anims) == "table" then
		for _,anim in pairs(anims) do
			if isanim(anim,entity) then
				return true
			end
		end
	else
		return isanim(anims,entity)
	end
	
	return false
end

function SearchForReturnAnim(anims,entity)
	if type(anims) == "table" then
		for anim,_ in pairs(anims) do
			if isanim(anim,entity) then
				return anim
			end
		end
	else
		return isanim(anims,entity) and anims
	end
end


function GetPlayerSeed()
    if GLOBAL.TheWorld and GLOBAL.TheWorld.net and GLOBAL.TheWorld.net.components.shardstate then
       return GLOBAL.TheWorld.net.components.shardstate:GetMasterSessionId() 
    end
end

-- 两点间的距离
function GetDist(x1,y1,x2,y2)
    return math.sqrt((x1 - x2) * (x1 - x2) + (y1 - y2) * (y1 - y2))
end

-- 判断测试服
function IsDSTBeta()
    -- Branch = "staging" 测试服
    return GLOBAL.BRANCH ~= "release"
end

-- 空表
function IsEmpty(t)
    if t == nil or (type(t) == "table" and #t == 0 ) then
        return true
    end
    return false
end

-- 仇恨目标
function GetAggro(ent)
    return ent and ent:IsValid() and ent.replica.combat and ent.replica.combat:GetTarget()
end

function GetLeader(ent)
    return ent and ent:IsValid() and ent.replica.follower and ent.replica.follower:GetLeader()
end

-- 方向计算器
-- direction可选up（默认） down left right
function GetDirectionAToB(Dist, Source, Direction)
    if Direction == "left" or Direction == "right" then
        local tmp = {x = Dist.x, z = Dist.z}
        Dist.x = Source.z - tmp.z + Source.x
        Dist.z = tmp.x - Source.x + Source.z
    end
    local numy = Dist.z - Source.z
    local numx = Dist.x - Source.x
    local absx = math.abs(numx)
    local absy = math.abs(numy)
    if absx == 0 and absy == 0 then
        return 0.5, 0.5
    end
    if absx > absy then
        numx = numx / absx
        numy = numy / absx
    else
        numx = numx / absy
        numy = numy / absy
    end
    if Direction == "down" or Direction == "right" then
        return -numx/2, -numy/2
    else
        return numx/2, numy/2
    end
end



-- 生成位置标记
function SpawnMark(inst)
    if inst and inst:IsValid() then
        local x,y,z = inst:GetPosition():Get()
        local arrow = GLOBAL.SpawnPrefab("huxi_arrow"):standUp()
        local circle = GLOBAL.SpawnPrefab("hrange"):setColor("GREEN"):setRadius(1.2)
        arrow.Transform:SetPosition(x, 0, z)
        circle.Transform:SetPosition(x, 0, z)
        return {arrow, circle}
    end
end

-- 擦除标记
for _,prefab in pairs({"huxi_arrow", "hrange", "huxi_sign", "huxi_archive_resonator", "huxi_mushgnome", "huxi_tumbleweed"
})do
    AddPrefabPostInit(prefab, function(inst)
        inst:DoTaskInTime(0.5, function()
            if inst and inst:IsValid() and inst.spawntime < 0.4 then
                inst:Remove()
            end
        end)
    end)
end


-- 获取耐久
function GetPerc(inst)
    local i = 100
    local classified = type(inst)=="table" and inst.replica and inst.replica.inventoryitem and inst.replica.inventoryitem.classified
    if classified then
        if inst:HasOneOfTags({"fresh", "show_spoilage"}) and classified.perish then
            i = math.floor(classified.perish:value() / 0.62)
        elseif classified.percentused then
            i = classified.percentused:value()
        end
    end
    return i
end

-- 获取上值
function GetUpvalueHelper(entry_fn, entry_name)
    local i = 1
    while true do
        local name, value = GLOBAL.debug.getupvalue(entry_fn, i)
        if name == entry_name then
            return value, i
        elseif name == nil then
            return
        end
        i = i + 1
    end
end

function GetActiveItem()
    return GLOBAL.ThePlayer and GLOBAL.ThePlayer.replica.inventory and GLOBAL.ThePlayer.replica.inventory:GetActiveItem()
end

local hoverer_funcs_in,hoverer_funcs_out = {},{}
AddClassPostConstruct("widgets/hoverer",function(hoverer)
	local _Hide = hoverer.text.Hide
	local _SetString = hoverer.text.SetString
	hoverer.text.SetString = function(text, str, ...)
		local target = GLOBAL.TheInput:GetHUDEntityUnderMouse()
        local item = target and target.widget and target.widget.parent and target.widget.parent.item
		local prefab = item and item.prefab
		local bind = prefab and GLOBAL.ThePlayer or GLOBAL.TheInput:GetWorldEntityUnderMouse()
		prefab = prefab or (bind and bind.prefab)
		if prefab and bind then
            for _, f in pairs(hoverer_funcs_in)do
                if type(f) == "function" then
                    str = f(item or bind, prefab, bind, str) or str
                end
            end
        end
		return _SetString(text, str, ...)
	end
	
	hoverer.text.Hide = function(...)
		for _, f in pairs(hoverer_funcs_out)do
            if type(f) == "function" then
                f()
            end
        end
		return _Hide(...)
	end
end)
-- func(target, prefab, bind, str)
-- warn:target need judge table!
function AddHovererFuncAndIsOut(func, out, name)
    local t = out and hoverer_funcs_out or hoverer_funcs_in
    if name then
        if not t[name] then
            t[name] = func
        end
    else
        table.insert(t, func)
    end
end
function RemoveHovererFuncAndIsOut(out, name)
    local t = out and hoverer_funcs_out or hoverer_funcs_in
    t[name] = nil
end

local player_func_in, player_func_out = {},{}
AddPrefabPostInit("world", function(world)
    world:ListenForEvent("playeractivated", function(world, player)
        if player == GLOBAL.ThePlayer then
            for _,p_func in pairs(player_func_in)do
                p_func(player, world)
            end
        end
    end)
end)

-- func(GLOBAL.ThePlayer, GLOBAL.TheWorld)
function AddPlayerActivatedFunc(func, out)
    if type(func) ~= "function" then return end
    if out then
        table.insert(player_func_out, func)
    else
        table.insert(player_func_in, func)
    end
end
